/*
 * $Id: mod_svn.c 7557b5d4d82b 2012/12/02 09:07:16 pgurumur $
 *
 * Copyright (c) 2006, 2007, 2008 Prabhu Gurumurthy <pgurumur@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * version "$Revision: 7557b5d4d82b $"
 * author "$Author: pgurumur $"
 */

#include "svn.h"

int
init_svn(ClientData cdata, Tcl_Interp *interp, int argc, Tcl_Obj *const *argv)
{
   int retval = TCL_ERROR;
   int length = 0;
   struct _tclsvn *ptr = (struct _tclsvn *)cdata;
   if (argc < 2) {
      Tcl_WrongNumArgs(interp, 1, argv, "command [ options ] url");
   }
   else {
      const char *program = Tcl_GetStringFromObj(argv[0], &length);
      if (argc >= 2) {
         const char *command = Tcl_GetStringFromObj(argv[1], &length);
         if ((strcmp(command, "list") == 0) || (strcmp(command, "ls") == 0)) {
            if (ptr->client == 0)
               error("%s initialize client first", __func__);

            if (argc >= 3) {
               const char *dirdepth = NULL;
               const char *url = Tcl_GetStringFromObj(argv[2], &length);
               svn_depth_t depth = svn_depth_immediates;
               if (argc == 4) {
                  dirdepth = Tcl_GetStringFromObj(argv[3], &length);
                  if (strcasecmp(dirdepth, "empty") == 0)
                     depth = svn_depth_empty;
                  else if (strcasecmp(dirdepth, "files") == 0)
                     depth = svn_depth_files;
                  else if (strcasecmp(dirdepth, "immediates") == 0)
                     depth = svn_depth_immediates;
                  else if (strcasecmp(dirdepth, "infinity") == 0)
                     depth = svn_depth_infinity;
               }

               svn_opt_revision_t peg;
               peg.kind = svn_opt_revision_unspecified;
               ptr->rev.kind = svn_opt_revision_head;

               Tcl_Obj *result = Tcl_NewDictObj();
               ptr->error = svn_client_list2(url, &peg, &ptr->rev, depth,
                     SVN_DIRENT_ALL, TRUE, recv_cb, result, ptr->ctx,
                     ptr->pool);
               if (ptr->error)
                  error("%s: svn_client_list2", __func__);

               Tcl_SetObjResult(ptr->interp, result);
               retval = TCL_OK;
            }
         }
         else if (strcmp(command, "help") == 0) {
            printf("%s commands url/path [ options ]\n", program);
            printf("commands: \n");
            printf("\t help\n");
            printf("\t list | ls \n");
            printf("\t checkout | co \n");
            printf("\t update | up \n");
            printf("\t status \n");
            printf("\t info \n");
            printf("\t client \n");
            printf("\t add \n");
            retval = TCL_OK;
         }
         else if ((strcmp(command, "checkout") == 0) || (strcmp(
                     command, "co") == 0)) {
            if (ptr->client == 0)
               error("%s initialize client first", __func__);

            if (argc >= 3) {
               const char *url = Tcl_GetStringFromObj(argv[2], &length);
               const char *path = Tcl_GetStringFromObj(argv[3], &length);
               long revision = 0;
               svn_opt_revision_t rev;
               if (argc == 5) {
                  if (Tcl_GetLongFromObj(ptr->interp,
                           argv[4], &revision) == TCL_OK)
                     rev.value.number = (svn_revnum_t)revision;
                  else
                     error("%s: Tcl_GetLongFromObj: ", __func__);

               }

               if (revision == 0)
                  rev.kind = svn_opt_revision_head;
               else
                  rev.kind = svn_opt_revision_number;

               svn_revnum_t result_rev;
               ptr->ctx->notify_func2 = notify_func;
               ptr->error = svn_client_checkout3(&result_rev,
                     url, path, &rev, &rev, svn_depth_unknown, FALSE, FALSE,
                     ptr->ctx, ptr->pool);
               if (ptr->error)
                  error("%s: svn_client_checkout3: ", __func__);

               Tcl_SetObjResult(ptr->interp, Tcl_NewIntObj((long)result_rev));
               retval = TCL_OK;
            }
            else
               Tcl_WrongNumArgs(
                     interp, 1, argv, "[ checkout | co ] url path");

         }
         else if ((strcmp(command, "update") == 0) || (strcmp(
                     command, "up") == 0)) {
            if (ptr->client == 0) 
               error("%s initialize client first", __func__);

            if (argc == 3) {
               int ix = 0;
               const char *path = Tcl_GetStringFromObj(argv[2], &length);
               apr_array_header_t *paths = apr_array_make(
                     ptr->pool, 1, sizeof( const char *));
               *(char **)apr_array_push(paths) = apr_pstrdup(ptr->pool, path);

               apr_array_header_t *result_revs = NULL;
               svn_opt_revision_t rev;
               rev.kind = svn_opt_revision_head;

               ptr->error = svn_client_update3(&result_revs, paths,
                     &rev, svn_depth_unknown, FALSE, FALSE, FALSE,
                     ptr->ctx, ptr->pool);
               if (ptr->error)
                  error("%s: svn_client_update3: ", __func__);

               Tcl_Obj *list = Tcl_NewListObj(0, NULL);
               for (ix = 0; ix < result_revs->nelts; ++ix) {
                  Tcl_Obj *revision = Tcl_NewIntObj(
                        (long)((svn_revnum_t *)result_revs->elts)[ix]);
                  Tcl_IncrRefCount(revision);
                  Tcl_ListObjAppendElement(ptr->interp, list, revision);
                  Tcl_DecrRefCount(revision);
               }

               Tcl_SetObjResult(ptr->interp, list);
               retval = TCL_OK;
            }
            else
               Tcl_WrongNumArgs(interp, 1, argv, "[ update | up ] path");
         }
         else if ((strcmp(command, "commit") == 0) || (strcmp(
                     command, "ci") == 0)) {
            if (ptr->client == 0)
               error("%s initialize client first", __func__);

            if (argc == 4) {
               svn_commit_info_t *ci = NULL;
               apr_array_header_t *paths = apr_array_make(ptr->pool, 1,
                     sizeof(const char *));
               *(char **)apr_array_push(paths) = apr_pstrdup(
                     ptr->pool, Tcl_GetStringFromObj(argv[2], &length));

               const char *mesg = Tcl_GetStringFromObj(argv[3], &length);
               ptr->ctx->log_msg_func3 = logmsg_cb;
               ptr->ctx->log_msg_baton3 = (void *)mesg;

               ptr->error = svn_client_commit4(&ci, paths,
                     svn_depth_infinity, FALSE, FALSE, NULL, NULL, ptr->ctx,
                     ptr->pool);
               if (ptr->error)
                  error("%s: svn_client_commit4: ", __func__);

               Tcl_SetObjResult(ptr->interp, Tcl_NewIntObj(ci->revision));
               retval = TCL_OK;
            }
            else
               Tcl_WrongNumArgs(interp, 1, argv,
                     "[ commit | ci ] path message");
         }
         else if (strcmp(command, "client") == 0) {
            if (ptr->client == 0) {
               if (argc >= 2)
                  subversion_client(program);

               const char *username = NULL;
               const char *password = NULL;

               if (argc == 3)
                  username = Tcl_GetStringFromObj(argv[2], &length);

               if (argc == 4) {
                  username = Tcl_GetStringFromObj(argv[2], &length);
                  password = Tcl_GetStringFromObj(argv[3], &length);
               }

               if (username == NULL)
                  username = getlogin();

               if (password == NULL)
                  password = "";

               svn_auth_set_parameter(ptr->ctx->auth_baton,
                     SVN_AUTH_PARAM_DEFAULT_USERNAME,
                     apr_pstrdup(ptr->pool, username));
               svn_auth_set_parameter(ptr->ctx->auth_baton,
                     SVN_AUTH_PARAM_DEFAULT_PASSWORD,
                     apr_pstrdup(ptr->pool, password));

               if ((argc >= 2) && (argc <= 4))
                  retval = TCL_OK;
               else {
                  Tcl_WrongNumArgs(interp, 1, argv,
                        "optional username and optional password");
                  retval = TCL_ERROR;
               }
            }
            else 
               Tcl_WrongNumArgs(interp, 1, argv, "[ username ] [ password ]");
         }
         else if (strcmp(command, "status") == 0) {
            if (ptr->client == 0)
               error("%s initialize client first", __func__);

            if (argc == 3) {
               const char *path = Tcl_GetStringFromObj(argv[2], &length);
               svn_opt_revision_t rev;
               rev.kind = svn_opt_revision_head;

               Tcl_Obj *result = Tcl_NewDictObj();
               svn_t->error = svn_client_status4(NULL, path, &rev, status_cb,
                     result, svn_depth_empty, TRUE, TRUE, FALSE, FALSE, NULL,
                     svn_t->ctx, svn_t->pool);
               if (svn_t->error)
                  error("%s svn_client_status4 ", __func__);

               Tcl_SetObjResult(ptr->interp, result);
               retval = TCL_OK;
            }
            else
               Tcl_WrongNumArgs(interp, 1, argv, "status path");
         }
         else if (strcmp(command, "info") == 0) {
            if (ptr->client == 0)
               error("%s initialize client first", __func__);

            if (argc == 3) {
               const char *path = Tcl_GetStringFromObj(argv[2], &length);

               Tcl_Obj *result = Tcl_NewDictObj();
               svn_opt_revision_t peg;
               peg.kind = svn_opt_revision_unspecified;
               ptr->rev.kind = svn_opt_revision_head;

               ptr->error = svn_client_info2(path, &peg, &ptr->rev,
                     info_cb, result, svn_depth_empty, NULL, ptr->ctx,
                     ptr->pool);
               if (ptr->error)
                  error("%s svn_client_info2 ", __func__);

               Tcl_SetObjResult(ptr->interp, result);
               retval = TCL_OK;
            }
            else
               Tcl_WrongNumArgs(interp, 1, argv, "info 'working dir path'");
         }
         else if (strcmp(command, "add") == 0) {
            if (ptr->client == 0)
               error("%s initialize client first", __func__);

            if (argc == 3) {
               const char *path = Tcl_GetStringFromObj(argv[2], &length);
               svn_depth_t depth = svn_depth_infinity;

               ptr->error = svn_client_add4(path, depth, FALSE, FALSE, 
                     FALSE, ptr->ctx, ptr->pool);
               if (ptr->error)
                  error("%s svn_client_add4 ", __func__);

               retval = TCL_OK;
            }
            else
               Tcl_WrongNumArgs(interp, 1, argv, "add path/file");
         }
         else if (strcmp(command, "version") == 0) {
            const svn_version_t *version = svn_client_version();
            if (version) {
               char buffer[MAXLINE];
               memset(buffer, MAXLINE, '\0');

               snprintf(buffer, MAXLINE, "%d.%d.%d %s", version->major,
                     version->minor, version->patch, version->tag);

               Tcl_SetObjResult(ptr->interp, Tcl_NewStringObj(buffer,
                        strlen(buffer)));

               retval = TCL_OK;
            }
         }
         else {
            Tcl_WrongNumArgs(interp, 1, argv, "unknown command");
         }
      }
   }

   return retval;
}

void
delete_svn(ClientData data)
{
   struct _tclsvn *stateptr = (struct _tclsvn *)data;
   svn_auth_set_parameter(stateptr->ctx->auth_baton, "username", NULL);
   svn_auth_set_parameter(stateptr->ctx->auth_baton, "password", NULL);
   svn_error_clear(svn_t->error);
   svn_pool_destroy(stateptr->pool);
   Tcl_Free((char *)stateptr);
}
